using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using Framework;

namespace SchemaTrading
{
    #region Enums (property)
    public enum EData
    {
        //Raw Data
        High,
        Low,
        Open,
        Close,

        //True Range
        ATR,
        TrueRange,

        //Directional Movement
        PlusDM,
        MinusDM,
        AvgPlusDM,
        AvgMinusDM,

        //Directional Index
        PlusDI,
        MinusDI,
        ADX_Item,
        ADX,
        StopLoss,

        //Trading
        Total,
        Held,
        Long,
        Short,
        Outlook,
        Notes,
        StopLong,
        StopShort,
        Returns,     //PrevTotal/CurrentTotal
        ExpAdjRet,   //Returns/Exposure
        LongQty,
        ShortQty,
        Exposure,
        PointValue
    }
    public enum EDecision
    {
        //Long
        Long,
        Long1,
        Long2,
        Long3,

        //Short
        Short,
        Short1,
        Short2,
        Short3,

        //StopLoss
        Stop
    }
    #endregion


    //Table-Mapping Class (Customisable half)
    public partial class CData
    {
        #region Constructors (Public)
        //Default Connection String
        public CData() : base() {}

        //Alternative Connection String
        public CData(CDataSrc dataSrc) : base(dataSrc) {}
        
        //Hidden  (UI code should use cache instead)
        protected internal CData(int dataId) : base(dataId) {}
        protected internal CData(CDataSrc dataSrc, int dataId) : base(dataSrc, dataId) {}
        protected internal CData(CDataSrc dataSrc, int dataId, IDbTransaction txOrNull) : base(dataSrc, dataId, txOrNull) { }
        #endregion

        #region Default Values
        protected override void InitValues_Custom()
        {
            //e.g. m_sampleDateCreated = DateTime.Now
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CSeries Series { get { return CSeries.Cache.GetById(this.DataSeriesId); } } 

        //Relationships - Collections (e.g. children)
        #endregion

        #region Cached Calculations
        public CData Prev { get; set; }
        public CData Next { get; set; }
        public CSimulation Simulation { get; set; }
        public void ClearAllCalcs()
        {
            _plusDI = double.NaN;
            _minusDI = double.NaN;
            _emaPlusDm = double.NaN;
            _emaMinusDm = double.NaN;
            _atr = double.NaN;
            this.Prev = null;
            this.Next = null;
        }
        #endregion

        #region Properties - Calculations
        //Constants
        public static int PERIOD_ADX = 14;
        public static int PERIOD_DM = 14;
        public static int PERIOD_TR = 14;
        public static double ALPHA_ADX { get { return 2.0 / (1.0 + PERIOD_ADX); } }
        public static double ALPHA_DM { get { return 2.0 / (1.0 + PERIOD_DM); } }
        public static double ALPHA_TR { get { return 2.0 / (1.0 + PERIOD_TR); } }
        public static double BETA_ADX { get { return 1.0 - ALPHA_ADX; } }
        public static double BETA_DM { get { return 1.0 - ALPHA_DM; } }
        public static double BETA_TR { get { return 1.0 - ALPHA_TR; } }
        public static double STOP_RATIO = 1.5;
        public static double MIN_ADX = 15;
        public static double MIN_ADX_CHANGE = 1;

        //Stop Loss
        public double StopLoss { get { return STOP_RATIO * AverageTrueRange; } }
        public double StopLong { get { return DataHigh - StopLoss; } }
        public double StopShort { get { return DataLow + StopLoss; } } 

        //ADX - see http://en.wikipedia.org/wiki/Average_Directional_Index
        private double _adx = double.NaN;
        public double ADX //Exponential Moving Average of the term below
        {
            get
            {
                if (double.IsNaN(_adx))
                {
                    if (null == Prev)
                    {
                        if (null == Next || 0 == this.Prior.Count)
                            return 0.0;
                        return this.Prior.Last.ADX;
                    }
                    _adx = ALPHA_ADX * 100.0 * ADX_Item + BETA_ADX * Prev.ADX;
                }
                return _adx;
            }
        }
        public double ADX_Item { get { return Math.Abs(PlusDI - MinusDI) / (PlusDI + MinusDI); } }
        public bool ADXIsRising { get { return Prev != null && !double.IsNaN(Prev.ADX) && ADX > Prev.ADX && (ADX - Prev.ADX > MIN_ADX_CHANGE); } }

        //Directional Indicator
        private double _plusDI = double.NaN;
        private double _minusDI = double.NaN;
        public double PlusDI  { get { if (double.IsNaN(_plusDI))  _plusDI  = ExponentialMovingAverage_PlusDM  / AverageTrueRange; return _plusDI; } }
        public double MinusDI { get { if (double.IsNaN(_minusDI)) _minusDI = ExponentialMovingAverage_MinusDM / AverageTrueRange; return _minusDI; } }

        //Directional Movement
        public double PlusDM
        {
            get
            {
                if (UpMove > DownMove && UpMove > 0)
                    return UpMove;
                return 0;
            }
        }
        public double MinusDM
        {
            get
            {
                if (DownMove > UpMove && DownMove > 0)
                    return DownMove;
                return 0;
            }
        }
        private double UpMove   { get { return (null == Prev) ? double.NaN : this.DataHigh - Prev.DataHigh; } }
        private double DownMove { get { return (null == Prev) ? double.NaN : Prev.DataLow -  this.DataLow;  } }


        //Exponential Moving Average - see: http://en.wikipedia.org/wiki/Exponential_moving_average
        // - as properties
        double _emaPlusDm  = double.NaN;
        double _emaMinusDm = double.NaN;
        public double ExponentialMovingAverage_PlusDM  
        { 
            get 
            {
                if (double.IsNaN(_emaPlusDm))
                    if (null == Prev)
                    {
                        if (null == this.Next || 0 == this.Prior.Count)
                            _emaPlusDm = ALPHA_DM * PlusDM;
                        else
                            _emaPlusDm = this.Prior.Last.ExponentialMovingAverage_PlusDM;
                    }
                    else
                        _emaPlusDm = ALPHA_DM * PlusDM + BETA_DM * Prev.ExponentialMovingAverage_PlusDM; 
                return _emaPlusDm;  
            }
        }
        public double ExponentialMovingAverage_MinusDM
        {
            get
            {
                if (double.IsNaN(_emaMinusDm))
                    if (null == Prev)
                    {
                        if (null == Next || 0 == this.Prior.Count)
                            _emaMinusDm = ALPHA_DM * MinusDM;
                        else
                            _emaMinusDm = this.Prior.Last.ExponentialMovingAverage_MinusDM;
                    }
                    else
                        _emaMinusDm = ALPHA_DM * MinusDM + BETA_DM * Prev.ExponentialMovingAverage_MinusDM;
                return _emaMinusDm;
            }
        }


        //Average True Range
        private double _atr;
        public double AverageTrueRange
        {
            get
            {
                if (double.IsNaN(_atr))
                {
                    if (null == Prev)
                    {
                        if (null == Next || 0 == this.Prior.Count)
                            _atr = ALPHA_TR * TrueRange;
                        else
                            _atr = this.Prior.Last.AverageTrueRange;
                    }
                    else
                        _atr = ALPHA_TR * TrueRange + BETA_TR * Prev.AverageTrueRange;
                }
                return _atr;
            }
        }
        public double TrueRange
        {
            get
            {
                if (null == Prev)
                    return DataHigh - DataLow;
                return Max(DataHigh, Prev.DataClose) - Min(DataLow, Prev.DataClose);
            }
        }
        private static double Max(double a, double b) { return a > b ? a : b; }
        private static double Min(double a, double b) { return a < b ? a : b; }

        //Daily Returns
        public double Returns 
        {   
            get 
            {
                if (null == Prev || double.IsNaN(Prev.SimulatedTotal))
                    return double.NaN;
                return (SimulatedTotal - Prev.SimulatedTotal) / Prev.SimulatedTotal;
            }
        }
        public double Exposure { get { return Simulation.Exposure; } }
        public double ExpAdjRet { get { return (Exposure > 0) ? Returns / Exposure : double.NaN; } }

        #endregion

        #region DataToString
        //Data from Enum
        public string GetString(EData column)
        {
            object obj = GetData(column);
            if (column == EData.Notes)
                return Notes.ToString();

            double d = (double)obj;
            if (double.IsNaN(d))
                return string.Empty;
            switch (column)
            {
                case EData.Open:
                case EData.Close:
                case EData.High:
                case EData.Low:
                case EData.StopLong:
                case EData.StopShort:
                    return d.ToString("F" + Series.SeriesDecimalPlaces);

                case EData.ExpAdjRet:
                case EData.Returns:
                case EData.Exposure:
                    return d.ToString("P4").Replace(" ", string.Empty);

                case EData.Held:
                case EData.Long:
                case EData.Short:
                case EData.Total:
                    return d.ToString("C");

                case EData.PointValue:
                    return d.ToString("C4");

                case EData.Outlook:
                    return ((EOutlook)d).ToString();

                case EData.ADX:
                    return d.ToString("F1");

                case EData.TrueRange:
                case EData.PlusDM:
                case EData.MinusDM:
                case EData.LongQty:
                case EData.ShortQty:
                    return d.ToString("F3");

                case EData.ATR:
                case EData.AvgPlusDM:
                case EData.AvgMinusDM:
                case EData.PlusDI:
                case EData.MinusDI:
                case EData.ADX_Item:
                case EData.StopLoss:
                    return d.ToString("F6");

                default:
                    return d.ToString();
            }
        }
        public object GetData(EData column)
        {
            switch (column)
            {
                case EData.High: return DataHigh;
                case EData.Low: return DataLow;
                case EData.Open: return DataOpen;
                case EData.Close: return DataClose;
                case EData.ADX: return ADX;
                case EData.ADX_Item: return ADX_Item;
                case EData.ATR: return AverageTrueRange;
                case EData.AvgMinusDM: return ExponentialMovingAverage_MinusDM;
                case EData.AvgPlusDM: return ExponentialMovingAverage_PlusDM;
                case EData.MinusDI: return MinusDI;
                case EData.MinusDM: return MinusDM;
                case EData.PlusDI: return PlusDI;
                case EData.PlusDM: return PlusDM;
                case EData.TrueRange: return TrueRange;
                case EData.StopLoss: return StopLoss;
                case EData.ExpAdjRet: return ExpAdjRet;
                case EData.Returns: return Returns;
                case EData.Held: return SimulatedHeld;
                case EData.Long: return SimulatedLong;
                case EData.Short: return SimulatedShort;
                case EData.Total: return SimulatedTotal;
                case EData.Notes: return Notes;
                case EData.StopLong: return StopLong;
                case EData.StopShort: return StopShort;
                case EData.Outlook: return (double)Outlook;
                case EData.Exposure: return Exposure;
                case EData.ShortQty: return SimulatedShortQty;
                case EData.LongQty: return SimulatedLongQty;
                case EData.PointValue: return SimulatedPointValue;
                default: throw new Exception("Unknown enum for CData.GetData: " + column);
            }
        }
        public static string GetPropertyName(EData column)
        {
            switch (column)
            {
                case EData.High: return "DataHigh";
                case EData.Low: return "DataLow";
                case EData.Open: return "DataOpen";
                case EData.Close: return "DataClose";
                case EData.ADX: return "ADX";
                case EData.ADX_Item: return "ADX_Item";
                case EData.ATR: return "AverageTrueRange";
                case EData.AvgMinusDM: return "ExponentialMovingAverage_MinusDM";
                case EData.AvgPlusDM: return "ExponentialMovingAverage_PlusDM";
                case EData.MinusDI: return "MinusDI";
                case EData.MinusDM: return "MinusDM";
                case EData.PlusDI: return "PlusDI";
                case EData.PlusDM: return "PlusDM";
                case EData.TrueRange: return "TrueRange";
                case EData.StopLoss: return "StopLoss";
                case EData.ExpAdjRet: return "ExpAdjRet";
                case EData.Returns: return "Returns";
                case EData.Held: return "SimulatedHeld";
                case EData.Long: return "SimulatedLong";
                case EData.Short: return "SimulatedShort";
                case EData.Total: return "SimulatedTotal";
                case EData.Notes: return "Notes";
                case EData.StopLong: return "StopLong";
                case EData.StopShort: return "StopShort";
                case EData.Outlook: return "Outlook";
                case EData.Exposure: return "Exposure";
                case EData.ShortQty: return "SimulatedShortQty";
                case EData.LongQty: return "SimulatedLongQty";
                case EData.PointValue: return "SimulatedPointValue";
                default: throw new Exception("Unknown enum for CData.GetPropertyName: " + column);
            }
        }
        #endregion

        #region Properties - Decision Levels
        public bool Long1 { get { return PlusDI >= MinusDI; } }
        public bool Long2 { get { return ADX >= MIN_ADX; } }
        public bool Long3 { get { return ADXIsRising; } }
        public bool Long  { get { return Long1 && Long2 && Long3; } }

        public bool Short1 { get { return MinusDI >= PlusDI; } }
        public bool Short2 { get { return ADX > MIN_ADX; } }
        public bool Short3 { get { return ADXIsRising; } }
        public bool Short { get { return Short1 && Short2 && Short3; } }

        public bool Stop 
        { 
            get 
            { 
                if (null == Prev || double.IsNaN(Prev.DataClose))
                    return false;
                double change = DataClose - Prev.DataClose;
                if (Long1)  return change > StopLoss;
                if (Short1) return change < StopLoss;
                return false;
            } 
        }


        public string GetString(EDecision column) { return GetDecision(column).ToString(); }
        public bool GetDecision(EDecision column)
        {
            switch (column)
            {
                case EDecision.Long:   return Long;
                case EDecision.Long1:  return Long1;
                case EDecision.Long2:  return Long2;
                case EDecision.Long3:  return Long3;
                case EDecision.Short:  return Short;
                case EDecision.Short1: return Short1;
                case EDecision.Short2: return Short2;
                case EDecision.Short3: return Short3;
                case EDecision.Stop: return Stop;
                default: throw new Exception("Unknown enum for CData.GetDecision: " + column);
            }
        }
        public static string GetTooltip(EDecision column)
        {
            switch (column)
            {
                case EDecision.Long: return "Long1 AND Long2 AND Long3";
                case EDecision.Long1: return "PlusDI >= MinusDI";
                case EDecision.Long2: return "ADX >= " + MIN_ADX;
                case EDecision.Long3: return "ADX is rising, and has risen by more than " + MIN_ADX_CHANGE;
                case EDecision.Short: return "Short1 AND Short2 AND Short3";
                case EDecision.Short1: return "MinusDI >= PlusDI";
                case EDecision.Short2: return "ADX > " + MIN_ADX;
                case EDecision.Short3: return "ADX is rising, and has risen by more than " + MIN_ADX_CHANGE;
                case EDecision.Stop: return "DataClose - Prev.DataClose > StopLoss";
                default: throw new Exception("Unknown enum for CData.GetDecision: " + column);
            }
        }
        public static string GetPropertyName(EDecision column)
        {
            switch (column)
            {
                case EDecision.Long: return "Long";
                case EDecision.Long1: return "Long1";
                case EDecision.Long2: return "Long2";
                case EDecision.Long3: return "Long3";
                case EDecision.Short: return "Short";
                case EDecision.Short1: return "Short1";
                case EDecision.Short2: return "Short2";
                case EDecision.Short3: return "Short3";
                case EDecision.Stop: return "Stop";
                default: throw new Exception("Unknown enum for CData.GetPropertyName: " + column);
            }
        }
        #endregion

        #region Trading Simulation - Shortcuts
        public double SimulatedHeld { get { return Simulation.Held;  } }
        public double SimulatedLong { get { return Simulation.LongCv; } }
        public double SimulatedLongQty { get { return Simulation.LongQty; } }
        public double SimulatedShort { get { return Simulation.ShortCv; } }
        public double SimulatedShortQty { get { return Simulation.ShortQty; } }
        public double SimulatedTotal { get { return Simulation.Total; } }
        public double SimulatedProfit { get { return Simulation.Profit; } }
        public CNoteList Notes { get { return Simulation.Notes; } }
        public EOutlook Outlook { get { return Simulation.Outlook; } }
        public double SimulatedPointValue { get { return Simulation.PointValue; } }

        private CDataList _prior = null;
        private CDataList Prior 
        { 
            get 
            {
                if (null == _prior)
                {
                    _prior = this.SelectPrior();
                    _prior.DoCalcs();
                }
                return _prior; 
            } 
        }
        #endregion

        #region Custom Database Queries
        //(Not normally required for cached classes, use list class for searching etc)
        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)
        public CDataList SelectPrior() { return SelectPrior(this.DataDate, this.DataSeriesId); }
        public CDataList SelectPrior(DateTime date, int seriesId)  
        {
            CCriteriaList where = new CCriteriaList("DataSeriesId", seriesId);
            where.Add("DataDate", ESign.LessThan, this.DataDate);
            return SelectWhere(where);        
        }
        #endregion

        #region Searching (Optional)
        //For cached classes, custom seach logic resides in static methods on the list class
        // e.g. CData.Cache.Search("...")

        //See also the auto-generated methods based on indexes
        //' e.g. CData.Cache.GetBy...
        #endregion

        #region Caching Details
        //Filters
        public static DateTime FROM_DATE = DateTime.MinValue;
        public static DateTime TO_DATE = DateTime.MinValue;

        //Cache data
        private static CDataList LoadCache()  
        {   
            CCriteriaList where = new CCriteriaList();
            if (FROM_DATE != DateTime.MinValue) where.Add("DataDate", ESign.GreaterThanOrEq, FROM_DATE);
            if (TO_DATE   != DateTime.MinValue) where.Add("DataDate", ESign.LessThanOrEq,    TO_DATE);
            return new CData().SelectWhere(where);

        }
        //Cache Timeout
        private static void SetCache(string key, CDataList value)
        {
            if (null != value)  
                value.Sort(); 
            CCache.Set(key, value);    //Optional parameter can override timeout (otherwise uses config-settings, which default to 3hrs)
        }
        //Helper Method
        private CData CacheGetById(CDataList list)  { return list.GetById(this.DataId);    }
        #endregion

        #region Cloning
        public CData Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CData copy = new CData(this, target);
            copy.Save(txOrNull);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.DataId);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this.Example)
        }
        #endregion
    }
}